﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Globalization;

using BizElements.Core;
using BizElements.BusinessLayer;
using BizElements.TemplateEngine;

namespace BizElements.Generator.EntityComponents
{
    /// <summary>Enumerates IDs of supported BizElements-based business object based component types.</summary>
    public static class BizLayerComponentType
    {
        #region Predefined types.

        /// <summary>Instance property contains a domain/business object.</summary>
        public static readonly ComponentType BusinessObject = new ComponentType(Guid.NewGuid());
        /// <summary>Instance property contains <see cref="BizElements.Core.IDbColumn"/> object.</summary>
        public static readonly ComponentType Field = new ComponentType(Guid.NewGuid());
        /// <summary>Instance property contains <see cref="BizElements.Core.IEntityCollection"/> object or any other generic collection of IEntity objects.</summary>
        public static readonly ComponentType Collection = new ComponentType(Guid.NewGuid());
        /// <summary>Instance property contains an array of domain/business objects.</summary>
        public static readonly ComponentType BusinessObjectArray = new ComponentType(Guid.NewGuid());
        /// <summary>Instance property contains <see cref="System.Reflection.PropertyInfo"/> object.</summary>
        public static readonly ComponentType SimpleProperty = new ComponentType(Guid.NewGuid());
        /// <summary>Instance property contains <see cref="System.Reflection.PropertyInfo"/> object.</summary>
        public static readonly ComponentType ComplexProperty = new ComponentType(Guid.NewGuid());

        #endregion
    }

    /// <summary>Builds <see cref="BizLayerComponentType.BusinessObject"/> input components.</summary>
    public sealed class BizLayerComponentBuilder : ITemplateInputComponentBuilder
    {
        #region ITemplateInputComponentBuilder.

        /// <summary>Builds <see cref="BizLayerComponentType.BusinessObject"/> input components.</summary>
        public ITemplateInputComponent Build(object instance)
        {
            Type objType = instance.GetType();
            return new BizLayerComponent(instance, objType.Name, objType.FullName);
        }

        /// <summary>Returns <b>true</b> if non-null reference is passed.</summary>
        public bool IsCompatibleItem(object instance)
        {
            return (instance != null);
        }

        #endregion
    }

    /// <summary>Builds <see cref="BizLayerComponentType.BusinessObjectArray"/> input components.</summary>
    public sealed class BusinessObjectArrayComponentBuilder : ITemplateInputComponentBuilder
    {
        #region ITemplateInputComponentBuilder.

        /// <summary>Builds <see cref="BizLayerComponentType.BusinessObjectArray"/> input components.</summary>
        /// <param name="instance">Array of objects.</param>
        public ITemplateInputComponent Build(object instance)
        {
            return new BizLayerComponent((object[])instance);
        }

        /// <summary>Returns <b>true</b> if an array is passed.</summary>
        public bool IsCompatibleItem(object instance)
        {
            return (instance != null) && (instance.GetType().IsArray);
        }

        #endregion
    }

    /// <summary>BizElements based template input component.</summary>
    /// <remarks><para>Root component of a hierarchy is an IBusinessObject object.</para>
    /// <para>The root component may contain the following subcomponents: entity collections and properties bound to entity/row fields.</para></remarks>
    public sealed class BizLayerComponent : TemplateInputComponentBase
    {
        #region CTor.

        /// <summary>Creates a root component. Instance property contains a domain/business object.</summary>
        public BizLayerComponent(object bizobj, string name, string fullName)
            : base(BizLayerComponentType.BusinessObject, bizobj, name, fullName, null)
        {
        }

        /// <summary>Creates a root component. Instance property contains an array of domain/business objects.</summary>
        public BizLayerComponent(object[] bizObjects)
            : base(BizLayerComponentType.BusinessObjectArray, bizObjects, "BusinessObjectArray", null, null)
        {
        }

        private BizLayerComponent(ComponentType type, object obj, string name, string fullName, ITemplateInputComponent parent)
            : base(type, obj, name, fullName, parent)
        {
        }

        #endregion

        #region Ensure/build sub-components list.

        /// <summary>Ensures that Subcomponents property is initialized.</summary>
        protected override void EnsureSubcomponents()
        {
            if (this.Subcomponents != null)
                return;

            this.Subcomponents = new List<ITemplateInputComponent>();            
            AppendCollectionSubComponents();
            AppendEntityFieldSubComponents();
            AppendCollectionFieldSubComponents();
            AppendBusinessObjectSubComponents();
            AppendSimpleBizObjPropertySubComponents();
            AppendNonParentComplexPropertySubComponents();
        }        

        private void AppendCollectionSubComponents()
        {
            bool isNotBizObj = (this.Instance is IDbColumn) || (this.Instance is IEntityCollection) || (this.Instance is IList);
            if (isNotBizObj)
                return;

            var allGetters = ArrayUtil.FindAll<List<PropertyInfo>, PropertyInfo>(this.Class.GetProperties(), (prop) => HasGetter(prop));
            foreach (PropertyInfo prop in allGetters)
            {
                bool isCollection = IsEntityCollection(prop.PropertyType);
                if (isCollection)
                {
                    object instance = prop.GetValue(this.Instance, null);
                    if (instance == null)
                    {
                        instance = Activator.CreateInstance(prop.PropertyType);
                    }

                    string fullPropertyPath = this.FullName + "." + prop.Name;
                    this.Subcomponents.Add(new BizLayerComponent(BizLayerComponentType.Collection, instance, prop.Name, fullPropertyPath, this));
                }
            }
        }

        private static bool IsEntityCollection(Type type)
        {
            if (null != type.GetInterface("IEntityCollection"))
                return true;

            bool isGenericEntityList = type.IsGenericType
                && (null != type.GetInterface("IList"))
                && (null != type.GetGenericArguments()[0].GetInterface("IEntity"));

            return isGenericEntityList;
        }

        private static bool HasGetter(PropertyInfo prop)
        {
            if (!prop.CanRead)
                return false;

            if (!ArrayUtil.IsNullOrEmpty(prop.GetIndexParameters()))
                return false;

            return true;
        }

        private void AppendEntityFieldSubComponents()
        {
            if (this.Instance is IEntity)
            {
                IEntity entity = this.Instance as IEntity;
                foreach (IDbColumn field in entity.Table.Columns)
                {
                    string fullPropertyPath = this.FullName + "." + field.PropertyName;
                    this.Subcomponents.Add(new BizLayerComponent(BizLayerComponentType.Field, field, field.PropertyName, fullPropertyPath, this));
                }
            }
        }

        private void AppendCollectionFieldSubComponents()
        {
            if (this.Type != BizLayerComponentType.Collection)
                return;

            IDbTable table = CollectionExtensionMethods.GetTable(this);
            foreach (IDbColumn field in table.Columns)
            {
                string fullPath = this.FullName + "." + field.PropertyName;
                this.Subcomponents.Add(new BizLayerComponent(BizLayerComponentType.Field, field, field.PropertyName, fullPath, this));
            }
        }        

        private void AppendBusinessObjectSubComponents()
        {
            if (this.Type != BizLayerComponentType.BusinessObjectArray)
                return;

            object[] bizobjArray = this.Instance as object[];
            if (bizobjArray != null)
            {
                foreach (object obj in bizobjArray)
                {
                    Type objType = obj.GetType();
                    this.Subcomponents.Add(new BizLayerComponent(BizLayerComponentType.BusinessObject, obj, objType.Name, objType.FullName, this));
                }
            }
        }

        private void AppendSimpleBizObjPropertySubComponents()
        {
            // May not be called before AppendEntityFieldSubComponents() is executed.
            if (this.Type != BizLayerComponentType.BusinessObject)
                return;

            var simpleNonDbProperties = ArrayUtil.FindAll<List<PropertyInfo>, PropertyInfo>(this.Class.GetProperties(), (prop) => IsNonDbPrimitiveGetterAndSetter(prop));
            foreach (PropertyInfo prop in simpleNonDbProperties)
            {
                string fullPropertyPath = this.FullName + "." + prop.Name;
                this.Subcomponents.Add(new BizLayerComponent(BizLayerComponentType.SimpleProperty, prop, prop.Name, fullPropertyPath, this));
            }
        }        

        private bool IsNonDbPrimitiveGetterAndSetter(PropertyInfo prop)
        {
            if (IsDbField(prop))
                return false;

            if (!HasGetterAndSetter(prop))
                return false;

            bool isNotInherited = (prop.DeclaringType == this.Class);
            if (!isNotInherited)
                return false;

            if (IsIndexer(prop))
                return false;

            bool isPrimitiveOrString = prop.PropertyType.IsPrimitive || (prop.PropertyType == typeof(string));
            if (isPrimitiveOrString)
                return true;

            if (IsNullable(prop.PropertyType))
            {
                Type underlyingType = prop.PropertyType.GetGenericArguments()[0];
                if (underlyingType.IsPrimitive)
                    return true;
            }

            return false;
        }

        private static bool IsIndexer(PropertyInfo prop)
        {
            return !ArrayUtil.IsNullOrEmpty(prop.GetIndexParameters());
        }

        private static bool HasGetterAndSetter(PropertyInfo prop)
        {
            return (prop.CanRead && prop.CanWrite);
        }

        private bool IsDbField(PropertyInfo prop)
        {
            return this.Subcomponents.Exists((sub) => (sub.Type == BizLayerComponentType.Field && sub.Name == prop.Name));
        }

        private static bool IsNullable(Type t)
        {
            return (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>));
        }

        private void AppendNonParentComplexPropertySubComponents()
        {
            // May not be called before AppendEntityFieldSubComponents() & AppendSimpleBizObjPropertySubComponents() are executed.
            if (this.Type != BizLayerComponentType.BusinessObject)
                return;

            IEntity bizEntity = this.Instance as IEntity;
            List<string> parentEntities = (bizEntity != null) ? new List<string>(bizEntity.Table.ParentEntityProperties) : new List<string>();

            var complexNonParentPropertis = ArrayUtil.FindAll<List<PropertyInfo>, PropertyInfo>(this.Class.GetProperties(), (prop) => IsComplexProperty(prop, parentEntities));
            foreach (PropertyInfo prop in complexNonParentPropertis)
            {
                string fullPropertyPath = this.FullName + "." + prop.Name;
                this.Subcomponents.Add(new BizLayerComponent(BizLayerComponentType.ComplexProperty, prop, prop.Name, fullPropertyPath, this));               
            }
        }

        private bool IsComplexProperty(PropertyInfo prop, List<string> skippedProperties)
        {
            if (skippedProperties.Contains(prop.Name))
                return false;

            if (IsDbField(prop))
                return false;

            if (!HasGetterAndSetter(prop))
                return false;

            if (IsIndexer(prop))
                return false;
            
            bool isSimpleType = null != prop.PropertyType.GetInterface("IConvertible");
            if (isSimpleType)
                return false;

            bool isArrayOrList = null != prop.PropertyType.GetInterface("IEnumerable");
            if (isArrayOrList)
                return false;

            var subProperties = prop.PropertyType.GetProperties();
            var setters = ArrayUtil.FindAll<List<PropertyInfo>, PropertyInfo>(subProperties, (p) => p.CanWrite);
            if (setters.Count == 0)
                return false;

            var indexers = ArrayUtil.FindAll<List<PropertyInfo>, PropertyInfo>(subProperties, (p) => IsIndexer(p));
            if (indexers.Count > 0)
                return false;

            bool hasDefaultCtor = (null != prop.PropertyType.GetConstructor(System.Type.EmptyTypes));
            if (!hasDefaultCtor)
                return false;

            return true;
        }

        #endregion

        #region GetComponentKeys.

        string[] computedComponentKeys;

        /// <summary>Gets component keys. 
        /// Full and short type name is returned for business objects.
        /// For properties mapped to database, property name is return, but in case of FK properties, parent entity and business object full class name is returned in addition to property name.
        /// Property name is return for simple and complex properties.
        /// For collections, children suffix is stripped from component name.
        /// For business object array empty array is returned.</summary>
        protected override string[] GetComponentKeys()
        {
            if (this.computedComponentKeys != null)
                return this.computedComponentKeys;

            if (this.Type == BizLayerComponentType.BusinessObject)
            {
                GetBusinessObjectComponentKeys();
            }
            else if (this.Type == BizLayerComponentType.Field)
            {
                GetFieldComponentKeys();
            }
            else if ((this.Type == BizLayerComponentType.SimpleProperty) || (this.Type == BizLayerComponentType.ComplexProperty))
            {
                this.computedComponentKeys = new string[] { this.Name, GetFullPropertyPath() };
            }
            else if (this.Type == BizLayerComponentType.Collection)
            {
                GetCollectionComponentKeys();
            }
            else
            {
                this.computedComponentKeys = new string[0];
            }

            return this.computedComponentKeys;
        }

        private void GetBusinessObjectComponentKeys()
        {
            Type bizObjClass = this.Instance.GetType();
            this.computedComponentKeys = new string[] 
            { 
                bizObjClass.FullName,
                bizObjClass.Name
            };
        }

        private void GetFieldComponentKeys()
        {            
            if (FieldCriteria.IsFk(this))
                GetFkFieldComponentKeys();
            else
                GetNonFkFieldComponentKeys();
        }

        private void GetFkFieldComponentKeys()
        {
            // HACK. We assume that entity classes have Entity suffix. 
            // We assume that child and parent domain object belong to sam namespace.
            string parentEntityClass = FkFieldExtensionMethods.ParentEntityClass(this);
            string parentBizobjClass = parentEntityClass;
            if (parentBizobjClass != null && parentBizobjClass.EndsWith("Entity"))
                parentBizobjClass = parentBizobjClass.Replace("Entity", "");

            string bizobjNamespace = this.Root.Instance.GetType().Namespace;
            parentBizobjClass = bizobjNamespace + "." + parentBizobjClass;
            string collectionFieldPropertyPath = GetPropertyPathInOtherDomainObjectIfSubcomponentBelongsToChildCollection(bizobjNamespace);
            if (string.IsNullOrEmpty(collectionFieldPropertyPath))
            {
                this.computedComponentKeys = new string[] 
                    { 
                        this.Name, 
                        GetFullPropertyPath(), 
                        parentEntityClass, 
                        parentBizobjClass 
                    };
            }
            else
            {
                this.computedComponentKeys = new string[] 
                    { 
                        this.Name, 
                        GetFullPropertyPath(), 
                        parentEntityClass, 
                        parentBizobjClass,
                        collectionFieldPropertyPath
                    };
            }
        }

        private void GetNonFkFieldComponentKeys()
        {
            string bizobjNamespace = this.Root.Instance.GetType().Namespace;
            string collectionFieldPropertyPath = GetPropertyPathInOtherDomainObjectIfSubcomponentBelongsToChildCollection(bizobjNamespace);
            if (string.IsNullOrEmpty(collectionFieldPropertyPath))
            {
                this.computedComponentKeys = new string[] 
                    { 
                        this.Name, 
                        GetFullPropertyPath() 
                    };
            }
            else
            {
                this.computedComponentKeys = new string[] 
                    { 
                        this.Name, 
                        GetFullPropertyPath(),
                        collectionFieldPropertyPath
                    };
            }
        }

        private void GetCollectionComponentKeys()
        {
            // HACK. There is no connection between collections and bizobj classes which may be stored inside them.
            // Maybe it would be better to just return "this.Name".
            string nameWithoutChildrenSuffix = this.Name.Replace("Children6", "").Replace("Children5", "").Replace("Children4", "").Replace("Children3", "").Replace("Children2", "").Replace("Children", "");

            // HACK. We assume that BL and ORM are used as namespaces and that entity classes have Entity suffix.
            string blNamespace = this.Parent.Instance.GetType().Namespace;
            string compositeEntityFullName = string.Format("{0}.{1}", blNamespace, nameWithoutChildrenSuffix);

            string ormNamespace = blNamespace.Replace(".BL", ".ORM");
            string weakEntityFullName = string.Format("{0}.{1}Entity", ormNamespace, nameWithoutChildrenSuffix);
            
            this.computedComponentKeys = new string[] 
            { 
                GetFullPropertyPath(), 
                nameWithoutChildrenSuffix,
                compositeEntityFullName,
                weakEntityFullName
            };
        }                

        private string GetFullPropertyPath()
        {
            string fullPropertyPath = string.Format("{0}.{1}", this.Parent.Instance.GetType().FullName, this.Name);
            return fullPropertyPath;
        }

        private string GetPropertyPathInOtherDomainObjectIfSubcomponentBelongsToChildCollection(string bizlayerNamespace)
        {
            if (this.Parent.Type != BizLayerComponentType.Collection)
                return null;

            // HACK. We assume that entity classes have Entity suffix.
            IDbTable table = CollectionExtensionMethods.GetTable(this.Parent);
            string domainObjectClass = table.NewEntity().GetType().Name.Replace("Entity", "");
            string propertyPath = string.Format("{0}.{1}.{2}", bizlayerNamespace, domainObjectClass, this.Name);
            return propertyPath;
        }

        #endregion
    }

    /// <summary>Contains common extension methods for all input components which may be used in templates.</summary>
    public static class CommonExtensionMethods
    {
        #region SplitNameToWords.

        /// <summary>Splits <see cref="ITemplateInputComponent.Name"/> to words.</summary>
        /// <param name="input">Input component whose name is first converted to camel case and then split to words.</param>
        public static string SplitNameToWords(this ITemplateInputComponent input)
        {
            return SplitNameToWords(input.Name);
        }

        /// <summary>Splits input string to words.</summary>
        /// <param name="inputName">Input string which is first converted to camel case and then split to words.</param>
        public static string SplitNameToWords(this string inputName)
        {
            string camelCase = NamingConventionHelper.ToCamelCase(inputName);
            StringBuilder words = new StringBuilder();
            for (int i = 0; i < camelCase.Length; i++)
            {
                char c = camelCase[i];
                if (char.IsUpper(c))
                {
                    if (!IsUpper(TryGet(camelCase, i - 1)))
                        words.Append(' ');

                    words.Append(char.ToLowerInvariant(c));
                }
                else
                {
                    words.Append(c);
                }
            }

            if (words.Length > 0)
                words[0] = char.ToUpperInvariant(words[0]);

            return words.ToString();
        }

        private static char? TryGet(string str, int index)
        {
            bool isWithinBounds = (index >= 0) && (index < str.Length);
            return (isWithinBounds) ? (char?)str[index] : null;
        }

        private static bool IsUpper(char? c)
        {
            return c.HasValue && char.IsUpper(c.Value);
        }

        #endregion
    }
   
    /// <summary>Contains extension methods for <see cref="BizLayerComponentType.Field"/> input components which may be used in templates.</summary>
    public static class FieldExtensionMethods
    {
        #region Field.

        static readonly Type[] SingleStringArg = new Type[] { typeof(string) };

        /// <summary>Invokes a GetFieldCaption() method for the given field.</summary>
        /// <param name="fieldComponent">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static string GetFieldCaption(this ITemplateInputComponent fieldComponent)
        {
            string caption = null;
            object bizobject = fieldComponent.Root.Instance;
            MethodInfo getFieldCaption = bizobject.GetType().GetMethod("GetFieldCaption", BindingFlags.Public, null, CallingConventions.HasThis, SingleStringArg, null);
            if (getFieldCaption != null)
            {
                caption = Convert.ToString(getFieldCaption.Invoke(bizobject, new object[] { fieldComponent.Name }), CultureInfo.InvariantCulture);
            }

            return caption;
        }

        /// <summary>Gets .NET type for the given field. Eg. Int32, String...</summary>
        /// <param name="fieldComponent">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static string DataType(this ITemplateInputComponent fieldComponent)
        {
            IDbColumn field = (IDbColumn)fieldComponent.Instance;
            return field.DataType.Name;
        }

        #endregion
    }

    /// <summary>Contains extension methods for <see cref="BizLayerComponentType.Field"/> compoments which represent a FK. Methods may be used in templates.</summary>
    public static class FkFieldExtensionMethods
    {
        #region Field + IsFk.

        /// <summary>Gets public entity class member that references parent entity object. Pascal case.</summary>
        /// <param name="foreignKeyField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static string ParentEntityProperty(this ITemplateInputComponent foreignKeyField)
        {
            IDbColumn fkField = (IDbColumn)foreignKeyField.Instance;
            DbRelation rel = GetFkMadeByField(fkField);
            string parentPropertyName = (rel != null) ? fkField.Table.GetParentProperty(rel, null) : "";
            return parentPropertyName;
        }

        private static DbRelation GetFkMadeByField(IDbColumn fkField)
        {
            foreach (DbRelation rel in fkField.Table.ForeignKeys)
            {
                if (rel.ChildForeignKey.Length == 1 && rel.ChildForeignKey[0].ColumnName == fkField.ColumnName)
                    return rel;
            }

            return null;
        }

        /// <summary>Gets the name of parent entity PK part property to which the given child FK field is linked.</summary>
        /// <param name="childForeignKeyField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static string Parent1stPkProperty(this ITemplateInputComponent childForeignKeyField)
        {
            IDbColumn fkField = (IDbColumn)childForeignKeyField.Instance;
            DbRelation rel = GetFkMadeByField(fkField);
            if (rel != null)
                return rel.ParentPrimaryKey[0].PropertyName;
            else
                return "";
        }

        /// <summary>Gets the name of 1st textual property in the parent entity.</summary>
        /// <param name="childForeignKeyField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static string ParentDefaultTextProperty(this ITemplateInputComponent childForeignKeyField)
        {
            IDbColumn fkField = (IDbColumn)childForeignKeyField.Instance;
            DbRelation rel = GetFkMadeByField(fkField);
            if (rel == null)
                return "";

            foreach (IDbColumn col in rel.Parent.Columns)
            {
                if (TypeUtil.IsText(col.DataType))
                    return col.PropertyName;
            }

            return rel.Parent.Columns[0].PropertyName;
        }

        /// <summary>Gets the fulle path of 1st textual property in the parent entity.</summary>
        /// <param name="childForeignKeyField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static string ParentDefaultTextPropertyFullPath(this ITemplateInputComponent childForeignKeyField)
        {
            IDbColumn fkField = (IDbColumn)childForeignKeyField.Instance;
            DbRelation rel = GetFkMadeByField(fkField);
            if (rel == null)
                return "";

            IDataObject parentEntity = rel.Parent.NewEntity() as IDataObject;
            if (parentEntity != null)
            {
                var descriptor = parentEntity.GetDataObjectDescriptor();
                if (!string.IsNullOrEmpty(descriptor.Fields.DefaultPropertyName))
                {
                    IDbColumn defaultParentField = parentEntity.Table.Columns.GetByPropertyName(descriptor.Fields.DefaultPropertyName);
                    string fullParentFieldPath = fkField.Table.GetParentProperty(rel, defaultParentField);
                    return fullParentFieldPath;
                }
            }

            foreach (IDbColumn col in rel.Parent.Columns)
            {
                if (TypeUtil.IsText(col.DataType))
                    return fkField.Table.GetParentProperty(rel, col);
            }

            return fkField.Table.GetParentProperty(rel, rel.Parent.Columns[0]);
        }

        /// <summary>Gets the name of the parent entity class.</summary>
        /// <param name="foreignKeyField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static string ParentEntityClass(this ITemplateInputComponent foreignKeyField)
        {
            IDbColumn fkField = (IDbColumn)foreignKeyField.Instance;
            DbRelation rel = GetFkMadeByField(fkField);
            if (rel == null)
                return "";
            else
                return rel.Parent.NewEntity().GetType().Name;
        }

        #endregion
    }

    /// <summary>Contains extension methods for <see cref="BizLayerComponentType.SimpleProperty"/> input components which may be used in templates.</summary>
    public static class SimplePropertyExtensionMethods
    {
        #region SimpleProperty.

        /// <summary>Gets simple property data type.</summary>
        /// <param name="simpleProperty">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="System.Reflection.PropertyInfo"/> object.</param>
        public static string PropertyDataType(this ITemplateInputComponent simpleProperty)
        {
            PropertyInfo prop = (PropertyInfo)simpleProperty.Instance;
            bool isPrimitiveOrString = prop.PropertyType.IsPrimitive || (prop.PropertyType == typeof(string));
            if (isPrimitiveOrString)
                return prop.PropertyType.ToString();
            else
                return prop.PropertyType.GetGenericArguments()[0].ToString();
        }

        #endregion
    }

    /// <summary>Contains extension methods for <see cref="BizLayerComponentType.ComplexProperty"/> input components which may be used in templates.</summary>
    public static class ComplexPropertyExtensionMethods
    {
        /// <summary>Gets complexy property class name.</summary>
        /// <param name="complexPropertyInfo">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="System.Reflection.PropertyInfo"/> object.</param>
        public static string PropertyTypeName(this ITemplateInputComponent complexPropertyInfo)
        {
            PropertyInfo prop = (PropertyInfo)complexPropertyInfo.Instance;
            return prop.PropertyType.Name;
        }
    }

    /// <summary>Contains methods/predicates which evaluate business object criteria.</summary>
    public static class BusinessObjectCriteria
    {
        #region HasChildren, ImplementsInterfaces, HasIntegerPK, HasForeignKeys.

        /// <summary>Returns <b>true</b> if business object has child collection.</summary>
        /// <param name="bizobj">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a business object.</param>
        public static bool HasChildren(ITemplateInputComponent bizobj)
        {
            var collections = bizobj.GetChildren(BizLayerComponentType.Collection);
            return !ArrayUtil.IsNullOrEmpty(collections);
        }

        /// <summary>Returns <b>true</b> if business object implements the given interfaces.</summary>
        /// <param name="bizobj">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a business object.</param>
        /// <param name="fullInterfaceNames">Array of interface names.</param>
        public static bool ImplementsInterfaces(ITemplateInputComponent bizobj, params string[] fullInterfaceNames)
        {
            Type t = bizobj.Instance.GetType();
            foreach (string interfaceName in fullInterfaceNames)
            {
                if (null == t.GetInterface(interfaceName))
                    return false;
            }

            return true;
        }

        /// <summary>Returns <b>true</b> if business object has single column integer PK.</summary>
        /// <param name="bizobj">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a business object.</param>
        public static bool HasIntegerPK(ITemplateInputComponent bizobj)
        {
            IEntity entity = bizobj.Instance as IEntity;
            bool hasSimplePk = (entity != null) && (entity.Table.PrimaryKey.Count == 1) && TypeUtil.IsInteger(entity.Table.PrimaryKey[0].DataType);
            return hasSimplePk;
        }

        /// <summary>Returns <b>true</b> if business object has single column integer PK.</summary>
        /// <param name="bizobj">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a business object.</param>
        public static bool HasIntegerPk(ITemplateInputComponent bizobj)
        {
            return HasIntegerPK(bizobj);
        }

        /// <summary>Returns <b>true</b> if tables that contains business object has foreign keys.</summary>
        /// <param name="bizobj">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a business object that implements IEntity interface.</param>
        public static bool HasForeignKeys(ITemplateInputComponent bizobj)
        {
            IEntity entity = bizobj.Instance as IEntity;
            return (entity.Table.ForeignKeys.Length > 0);
        }

        /// <summary>Returns <b>true</b> if table contains text field.</summary>
        /// <param name="bizobj">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a business object.</param>
        public static bool HasTextField(ITemplateInputComponent bizobj)
        {
            IEntity entity = bizobj.Instance as IEntity;
            foreach (IDbColumn col in entity.Table.Columns)
            {
                if (TypeUtil.IsText(col.DataType))
                    return true;
            }

            return false;
        }

        /// <summary>Returns <b>true</b> if table does not contain text field.</summary>
        /// <param name="bizobj">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a business object.</param>
        public static bool HasNoTextField(ITemplateInputComponent bizobj)
        {
            return !BusinessObjectCriteria.HasTextField(bizobj);
        }

        /// <summary>Returns true if the specified short class name exists in the same namespace and assembly as current business object.</summary>
        /// <param name="bizobj">Business object.</param>
        /// <param name="shortClassName">Short class name of another object.</param>
        /// <returns><b>true</b> if specified class name is found in same assembly and sam namespace; otherwise <b>false</b></returns>
        public static bool ExistsClassInSameNamespace(ITemplateInputComponent bizobj, string shortClassName)
        {
            string nameSpace = bizobj.Instance.GetType().Namespace;
            string fullClassName = nameSpace + "." + shortClassName;
            var asm = bizobj.Instance.GetType().Assembly;
            bool exists = null != asm.GetType(fullClassName);
            return exists;
        }

        #endregion
    }

    /// <summary>Commonly used methods used to define BizElements field criteria in template XML files.</summary>
    public static class FieldCriteria
    {
        #region IsPk, IsNotPk.

        /// <summary>Returns <b>true</b> if field is PK part.</summary>
        /// <param name="fieldComponent">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static bool IsPk(ITemplateInputComponent fieldComponent)
        {
            IDbColumn field = fieldComponent.Instance as IDbColumn;
            return (field != null) && field.IsPrimaryKeyPart;
        }

        /// <summary>Returns <b>true</b> if field is not PK part.</summary>
        /// <param name="fieldComponent">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static bool IsNotPk(ITemplateInputComponent fieldComponent)
        {
            return !IsPk(fieldComponent);
        }

        #endregion

        /// <summary>Returns <b>true</b> if field is an auto-incremented number of GUID.</summary>
        /// <param name="fieldComponent">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static bool IsSurrogatePk(ITemplateInputComponent fieldComponent)
        {
            // It would be more precis to test AutoIncrement flag for integer fields but for Oracle AutoIncrement is set via config file.
            IDbColumn field = (IDbColumn)fieldComponent.Instance;
            bool isIntOrGuidPk = field.IsPrimaryKeyPart && TypeUtil.IsInteger(field.DataType) || TypeUtil.IsGuid(field.DataType);
            
            // Check that table doesn't have composite PK.
            bool isSurrogate = isIntOrGuidPk && (field.Table.PrimaryKey.Count == 1);
            return isSurrogate;
        }

        /// <summary>Returns <b>true</b> if field is NOT an auto-incremented number of GUID.</summary>
        /// <param name="fieldComponent">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static bool IsNotSurrogatePk(ITemplateInputComponent fieldComponent)
        {
            return !IsSurrogatePk(fieldComponent);
        }

        #region IsFk, IsNotFk.

        /// <summary>Returns <b>true</b> if field is FK part.</summary>
        /// <param name="fieldComponent">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static bool IsFk(ITemplateInputComponent fieldComponent)
        {
            IDbColumn field = fieldComponent.Instance as IDbColumn;
            return (field != null) && field.IsForeignKeyPart;
        }

        /// <summary>Returns <b>true</b> if field is not FK part.</summary>
        /// <param name="fieldComponent">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static bool IsNotFk(ITemplateInputComponent fieldComponent)
        {
            return !IsFk(fieldComponent);
        }

        #endregion

        #region IsKey, IsNotKey.

        /// <summary>Returns <b>true</b> if field is PK or FK part.</summary>
        /// <param name="fieldComponent">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static bool IsKey(ITemplateInputComponent fieldComponent)
        {
            return IsPk(fieldComponent) || IsFk(fieldComponent);
        }

        /// <summary>Returns <b>true</b> if field is not PK or FK part.</summary>
        /// <param name="fieldComponent">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static bool IsNotKey(ITemplateInputComponent fieldComponent)
        {
            return !IsKey(fieldComponent);
        }

        #endregion

        #region IsText, IsNotText.

        /// <summary>Returns <b>true</b> if field is textual field.</summary>
        /// <param name="fieldComponent">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static bool IsText(ITemplateInputComponent fieldComponent)
        {
            IDbColumn field = fieldComponent.Instance as IDbColumn;
            return (field != null) && TypeUtil.IsText(field.DataType);
        }

        /// <summary>Returns <b>true</b> if field is not textual field.</summary>
        /// <param name="fieldComponent">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static bool IsNotText(ITemplateInputComponent fieldComponent)
        {
            return !IsText(fieldComponent);
        }

        #endregion

        #region IsNumber, IsNotNumber.

        /// <summary>Returns <b>true</b> if field is numberic field.</summary>
        /// <param name="fieldComponent">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static bool IsNumber(ITemplateInputComponent fieldComponent)
        {
            IDbColumn field = fieldComponent.Instance as IDbColumn;
            return (field != null) && TypeUtil.IsNumber(field.DataType);
        }

        /// <summary>Returns <b>true</b> if field is not numeric field.</summary>
        /// <param name="fieldComponent">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static bool IsNotNumber(ITemplateInputComponent fieldComponent)
        {
            return !IsNotNumber(fieldComponent);
        }

        #endregion

        #region IsDate, IsNotDate.

        /// <summary>Returns <b>true</b> if field is date field.</summary>
        /// <param name="fieldComponent">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static bool IsDate(ITemplateInputComponent fieldComponent)
        {
            IDbColumn field = fieldComponent.Instance as IDbColumn;
            return (field != null) && TypeUtil.IsDate(field.DataType);
        }

        /// <summary>Returns <b>true</b> if field is not date field.</summary>
        /// <param name="fieldComponent">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static bool IsNotDate(ITemplateInputComponent fieldComponent)
        {
            return !IsDate(fieldComponent);
        }

        #endregion

        #region IsMatch.

        /// <summary>Creates a predicate, ie. method delegate, which checks if column name matches give regular expression.</summary>
        public static Predicate<ITemplateInputComponent> IsMatch(string regexPattern)
        {
            return new Predicate<ITemplateInputComponent>(new FieldRegexCriteria(regexPattern).IsMatch);
        }

        private class FieldRegexCriteria
        {
            Regex rex;

            public FieldRegexCriteria(string regexPattern)
            {
                this.rex = new Regex(regexPattern);
            }

            public bool IsMatch(ITemplateInputComponent fieldComponent)
            {
                IDbColumn field = fieldComponent.Instance as IDbColumn;
                return (field != null) && this.rex.IsMatch(field.PropertyName);
            }
        }

        #endregion

        #region IsFkToRoot, IsNotFkToRoot.

        /// <summary>Returns <b>true</b> if field is used to establish relation/FK to <see cref="ITemplateInputComponent.Root"/> object.</summary>
        /// <param name="fieldComponent">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static bool IsFkToRoot(ITemplateInputComponent fieldComponent)
        {
            IDbColumn field = fieldComponent.Instance as IDbColumn;
            if (!field.IsForeignKeyPart)
                return false;

            IEntity root = fieldComponent.Root.Instance as IEntity;
            if (root == null)
                return false;

            var relations = ArrayUtil.FindAll<List<DbRelation>, DbRelation>(field.Table.ForeignKeys, (rel) => (rel.ChildForeignKey[0].ColumnName == field.ColumnName));
            if (relations.Count == 0)
                return false;

            if (relations[0].Parent.TableName == root.Table.TableName)
                return true;
            else
                return false;
        }

        /// <summary>Returns <b>true</b> if field is not used to establish relation/FK to <see cref="ITemplateInputComponent.Root"/> object.</summary>
        /// <param name="fieldComponent">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static bool IsNotFkToRoot(ITemplateInputComponent fieldComponent)
        {
            IDbColumn field = fieldComponent.Instance as IDbColumn;
            if (!field.IsForeignKeyPart)
                return true;

            IEntity root = fieldComponent.Root.Instance as IEntity;
            if (root == null)
                return true;

            var relations = ArrayUtil.FindAll<List<DbRelation>, DbRelation>(field.Table.ForeignKeys, (rel) => (rel.ChildForeignKey[0].ColumnName == field.ColumnName));
            if (relations.Count == 0)
                return true;

            if (relations[0].Parent.TableName == root.Table.TableName)
                return false;
            else
                return true;
        }

        #endregion

        #region IsBool, IsNotBool.

        /// <summary>Returns <b>true</b> if field is boolean field.</summary>
        /// <param name="fieldComponent">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static bool IsBool(ITemplateInputComponent fieldComponent)
        {
            IDbColumn field = fieldComponent.Instance as IDbColumn;
            if (field == null)
                return false;

            bool isBoolOrIsByteWhitIsPrefix = (field.DataType == typeof(bool))
                || (field.DataType == typeof(byte) && field.PropertyName.StartsWith("Is", StringComparison.InvariantCulture));
            return isBoolOrIsByteWhitIsPrefix;
        }

        /// <summary>Returns <b>true</b> if field is not boolean field.</summary>
        /// <param name="fieldComponent">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IDbColumn"/> object.</param>
        public static bool IsNotBool(ITemplateInputComponent fieldComponent)
        {
            return !IsBool(fieldComponent);
        }

        #endregion
    }

    /// <summary>Contains methods/predicates which evaluate entity collection criteria.</summary>
    public static class CollectionCriteria
    {
        #region HasIntegerPk.

        /// <summary>Returns <b>true</b> if entity collection has single column integer PK.</summary>
        /// <param name="entityCollection">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IEntityCollection"/> object or any other generic list of IEntity objects.</param>
        public static bool HasIntegerPk(ITemplateInputComponent entityCollection)
        {
            IDbTable table = CollectionExtensionMethods.GetTable(entityCollection);
            bool hasSimplePk = (table.PrimaryKey.Count == 1) && TypeUtil.IsInteger(table.PrimaryKey[0].DataType);
            return hasSimplePk;
        }

        #endregion

        #region HasSingleFieldPK.

        /// <summary>Returns <b>true</b> if entity collection has single column PK.</summary>
        /// <param name="entityCollection">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Core.IEntityCollection"/> object or any other generic list of IEntity objects.</param>
        public static bool HasSingleFieldPK(ITemplateInputComponent entityCollection)
        {
            IDbTable table = CollectionExtensionMethods.GetTable(entityCollection);
            bool hasSingleFieldPK = (table.PrimaryKey.Count == 1);
            return hasSingleFieldPK;
        }

        #endregion
    }

    /// <summary>Contains extension methods for <see cref="BizLayerComponentType.Collection"/> input components which may be used in templates.</summary>
    public static class CollectionExtensionMethods
    {
        /// <summary>Gets table that contains entities which belong to the collection.</summary>
        /// <param name="inputComponent">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a collection.</param>
        public static IDbTable GetTable(ITemplateInputComponent inputComponent)
        {
            if (inputComponent.Type != BizLayerComponentType.Collection)
                throw new ArgumentException("The provided component is not a collection of entities.");

            IEntityCollection entityCollection = inputComponent.Instance as IEntityCollection;
            if (entityCollection != null)
                return entityCollection.IDbTable;

            Type entityType = inputComponent.Class.GetGenericArguments()[0];
            Type tableType = entityType.GetProperty("Table").PropertyType;
            return (IDbTable)Activator.CreateInstance(tableType);
        }
    }
}
